#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/types.h>
#include <linux/kdev_t.h>
#include <linux/device.h>
#include <linux/ioctl.h>
#include <linux/uaccess.h>
#include <asm/uaccess.h>
#include <linux/time.h>

/* 《时间、延迟以及延缓操作》源码
    https://blog.csdn.net/qq_33242956/article/details/98057515
*/

MODULE_LICENSE("GPL");

#define CSDN_IOC_MAGIC 'x'
#define CSDN_IOC_GET _IO(CSDN_IOC_MAGIC, 0)
#define CSDN_IOC_W _IOW(CSDN_IOC_MAGIC, 1, int)
#define CSDN_IOC_R _IOR(CSDN_IOC_MAGIC, 2, int)
#define CSDN_IOC_WR _IOWR(CSDN_IOC_MAGIC, 3, int)

static int minor = 0; /* 次设备号 */
static dev_t csdn_dev;
static struct cdev csdn_cdev;
static struct class *csdn_class = NULL;
static struct device *csdn_device = NULL;

struct work_struct work;

static int csdn_open(struct inode *inode, struct file *filp)
{
    /* TODO */
    return 0;
}

static int csdn_release(struct inode *inode, struct file *filp)
{
    /* TODO */
    return 0;
}

static ssize_t csdn_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
    /* TODO */
    return 0;
}

static ssize_t csdn_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
    /* TODO */
    return 0;
}

static long csdn_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    int retval = 0;
    int ioctl_w_value = 0;
    int ioctl_r_value = 2222;

    pr_info("csdn_ioctl");
    if (_IOC_TYPE(cmd) != CSDN_IOC_MAGIC) {
        return -ENODEV;
    }

    if (_IOC_DIR(cmd) & _IOC_READ)
		retval = !access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd));
	else if (_IOC_DIR(cmd) & _IOC_WRITE)
		retval = !access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd));

    if (retval)
		return -EFAULT;

    switch (cmd)
    {
    case CSDN_IOC_GET:
        pr_info("CSDN_IOC_GET \n");
        schedule_work(&work);
        break;

    case CSDN_IOC_W:
        pr_info("CSDN_IOC_W \n");
        retval = copy_from_user(&ioctl_w_value, (int *)arg, sizeof(int));
        pr_info("retval: %d, ioctl_w_value: %d\n", retval, ioctl_w_value);
        break;

    case CSDN_IOC_R:
        pr_info("CSDN_IOC_R \n");
        retval = copy_to_user((void __user *)arg, (void *)&ioctl_r_value, sizeof(int));
        pr_info("retval: %d\n", retval);
        break;

    case CSDN_IOC_WR:
        pr_info("CSDN_IOC_WR \n");
        retval = copy_from_user(&ioctl_w_value, (int *)arg, sizeof(int));
        pr_info("retval: %d, ioctl_w_value: %d\n", retval, ioctl_w_value);

        ioctl_r_value = 3333;
        retval = copy_to_user((void __user *)arg, (void *)&ioctl_r_value, sizeof(int));
        pr_info("retval: %d\n", retval);
        break;

    default:
        pr_warn("unsupport cmd:0x%x\n", cmd);
        break;
    }

    return 0;
}

static struct file_operations csdn_fops = {
    .owner = THIS_MODULE,
    .open = csdn_open,
    .release = csdn_release,
    .read = csdn_read,
    .write = csdn_write,
    .unlocked_ioctl = csdn_ioctl,
};

/* 创建设备号 */
static int csdn_register_chrdev(void)
{
    int result;

    result = alloc_chrdev_region(&csdn_dev, minor, 1, "csdn_dev");
    if (result < 0) {
        pr_err("alloc_chrdev_region failed! result: %d\n", result);
        return result;
    }

    return 0;
}

/* 注册驱动 */
static int csdn_cdev_add(void)
{
    int result;

    cdev_init(&csdn_cdev, &csdn_fops);
    csdn_cdev.owner = THIS_MODULE;

    result = cdev_add(&csdn_cdev, csdn_dev, 1);
    if (result < 0) {
        pr_err("alloc_chrdev_region failed! result: %d\n", result);
        unregister_chrdev_region(csdn_dev, 1);
        return result;
    }

    return 0;
}

/* 创建设备节点 */
static int csdn_device_create(void)
{
    csdn_class = class_create(THIS_MODULE, "csdn_dev_class");
    if (IS_ERR(csdn_class)) {
        pr_err("class_create failed!\n");
        goto class_create_fail;
    }

    csdn_device = device_create(csdn_class, NULL, csdn_dev, NULL, "csdn_dev");
    if (IS_ERR(csdn_device)) {
        pr_err("device_create failed!\n");
        goto device_create_fail;
    }

    return 0;

device_create_fail:
    class_destroy(csdn_class);
class_create_fail:
    cdev_del(&csdn_cdev);
    unregister_chrdev_region(csdn_dev, 1);
    return -1;
}

static void notification_work(struct work_struct *work)
{
	pr_info("%s\n", __func__);
}

static __init int csdn_init(void)
{
    int result;

    pr_info("csdn_init\n");

    INIT_WORK(&work, notification_work);

    result = csdn_register_chrdev();
    if (result < 0) {
        return result;
    }

    result = csdn_cdev_add();
    if (result < 0) {
        return result;
    }

    result = csdn_device_create();
    if (result < 0) {
        return result;
    }

    return 0;
}

static __exit void csdn_exit(void)
{
    pr_info("csdn_exit\n");

    device_destroy(csdn_class, csdn_dev);
    class_destroy(csdn_class);
    cdev_del(&csdn_cdev);
    unregister_chrdev_region(csdn_dev, 1);
}

module_init(csdn_init);
module_exit(csdn_exit);